﻿using Nemerle.Collections;
using Nemerle.Text;

using System;
using System.IO;
using System.Collections.Generic;
using System.Console;
using System.Linq;
using System.Reflection;
using NRails;
using NRails.Migrations;
using NRails.Utils;
using NRails.Console;
using log4net.Config;
using System.Text.RegularExpressions;

using NRails.Database.Schema;

module Program
{
  public variant MigrationMode 
  {
      | CompiledAssembly {assemblyFile : string}
      | RawFiles { fileList : list[string]}
      | NotSet
  }

  Main() : void
  {
      mutable args = [];
      mutable unhandled = [];

      def app = log4net.Appender.ConsoleAppender();
      app.Layout = log4net.Layout.PatternLayout("%m %n");
      app.Threshold = log4net.Core.Level.Info;

      BasicConfigurator.Configure(app);

      def options = [
        Getopt.CliOption.NonOption(name = "command",
            aliases = [],
            handler = (c) => args = c :: args
        ),
        
        Getopt.CliOption.Unhandled(name = "", handler = (c) => unhandled = c :: unhandled),
        
        Getopt.CliOption.Flag(name = "--verbose",
            aliases = ["-v"],
            handler = () =>
            {
                app.Threshold = log4net.Core.Level.Debug;
                app.Layout = log4net.Layout.PatternLayout("%d - %m %n");
            },
        ),
        Getopt.CliOption.Flag(name = "--vverbose",
            aliases = ["-vv"],
            handler = () =>
            {
                app.Threshold = log4net.Core.Level.Debug;
                app.Layout = log4net.Layout.PatternLayout("%d %l - %m %n");
            },
        ),
      ];

      Getopt.Parse(options);
      args = args.Rev();
      unhandled = unhandled.Rev();
      
      match (args)
      {
          | "migrate" :: args => Migrate(args, unhandled);
          | "generate" :: args => Generate(args, unhandled);
          | _ => Console.WriteLine("Valid commands: migrate, generate.");
      }
  }

  Migrate(args : list[string], opts : list[string]) : void
  {
      mutable migrationMode = MigrationMode.NotSet();

      def migrationPathHandler(c)
      {
        def files = Directory.GetFiles(c, "*.n").ToNList();
        migrationMode = MigrationMode.RawFiles(files);
      }

      mutable allowMissing = false;
      
      def options = [
        Getopt.CliOption.String(name = "--migration-assembly",
            aliases = ["-ma"],
            handler = (c) =>
            {
                migrationMode = MigrationMode.CompiledAssembly(c);
            }
        ),
        Getopt.CliOption.String(name = "--migrations-path",
            aliases = ["-mp"],
            handler = migrationPathHandler,
        ),
        Getopt.CliOption.Flag(name = "--allow-missing",
            aliases = ["-am"],
            handler = () => allowMissing = true
        ),
        Getopt.CliOption.NonOption(name = "command args",  handler = _ => ())
      ];
      
      Getopt.Parse(Getopt.Error, options, NList.Flatten([args, opts]));
      when (migrationMode is MigrationMode.NotSet)
      {
          migrationPathHandler("Migrations")
      }
      
      def ma = match (migrationMode)
      {
          | MigrationMode.CompiledAssembly(migrationAssmbly) =>
              Assembly.LoadFile(Path.GetFullPath(migrationAssmbly));
          | MigrationMode.RawFiles(files) =>
              MigrationCompiler.Compile(files);
          | NotSet =>
              throw ArgumentException();
      }

      def migrationTypes = ma.GetTypes().Filter(t => t.IsSubclassOf(typeof(Migration)) && !t.IsAbstract);
      
      def migrator = Migrator(Engine.Instance);
      
      def makeMigration(migrationType)
      {
          Activator.CreateInstance(migrationType) :> IMigration;
      }
      
      def migrationDirection = match (args)
      {
        | [] => MigrationDirection.All();
        | ["up"] => MigrationDirection.Up();
        | ["down"] => MigrationDirection.Down();
        | [x] => MigrationDirection.Version(x);
        | _ => throw ArgumentException($"..$args");
      }

      migrator.Migrate(migrationDirection, migrationTypes.Map(makeMigration), allowMissing);
  }
  
    Generate(args : list[string], opts : list[string]) : void
    {
        mutable path = "Migrations";
        mutable ns = null;
        def options = [
            Getopt.CliOption.String(name = "--path",
                aliases = ["-p"],
                handler = c => path = c,
            ),
            Getopt.CliOption.String(name = "--namespace",
                aliases = ["-ns"],
                handler = (c) => ns = c,
            ),
            Getopt.CliOption.NonOption(name = "command args",  handler = _ => ())
        ];
        Getopt.Parse(Getopt.Error, options, NList.Flatten([args, opts]));

        if (string.IsNullOrEmpty(path) || Directory.Exists(path))
        {
            def findRootNamespace()
            {
                def files = Directory.GetFiles(Environment.CurrentDirectory, "*.nproj").ToNList();
                match (files)
                {
                    | file :: [] => 
                        def m = Regex(@"<RootNamespace>(.+)</RootNamespace>", RegexOptions.Multiline).Match(File.ReadAllText(file));
                        if (m.Success)
                        {
                            m.Groups[1].Value
                        }
                        else
                        {
                            Console.WriteLine($"RootNamespace not set, tried to get it from project file $file but was unable to find RootNamespace");
                            null
                        }
                    | [] =>
                        Console.WriteLine("RootNamespace not set, tried to get it from project file but project file is missing");
                        null
                    | _ =>
                        Console.WriteLine($"RootNamespace not set, tried to get it from project file but found more then one: ..$files");
                        null
                }
            }
            
            when (string.IsNullOrEmpty(ns))
                ns = findRootNamespace();

            when (ns != null)            
            match (args)
            {
                | ["migration"] => Generator().GenerateMigration(path, ns)
                | ["create"]
                | ["new"] =>
                    def migrator = Migrator(Engine.Instance);
                    def dbSchema = migrator.GetSchema();
                    Generator().CreateMigration(path, ns, dbSchema)

/* типа на будущее но все равно переделывать
      | [obj, name] with path = "Controllers"
      | [obj, name, path] =>
          match (string.IsNullOrEmpty(path) || Directory.Exists(path))
          {
            | true =>
                match (obj.ToLower())
                {
                  //| "view" => Generator().GenerateView(path, name)
                  | "controller" => Generator().GenerateController(path, name)
                }
            | false => Console.WriteLine($"Not existing path '$(path)'")
          }
*/
                | _ => throw ArgumentException($"..$args");
            }
        }
        else
        {
            Console.WriteLine($"Not existing path '$(path)'");
        }
    }
}
